Skip to content

GH-854 Add Monotonic Cubic Interpolation#1224

Open
JoltedJon wants to merge 9 commits intoRedot-Engine:masterfrom
JoltedJon:GH-854
Open

GH-854 Add Monotonic Cubic Interpolation#1224
JoltedJon wants to merge 9 commits intoRedot-Engine:masterfrom
JoltedJon:GH-854

Conversation

@JoltedJon
Copy link
Contributor

@JoltedJon JoltedJon commented Mar 13, 2026

Adds a Monotonic version of Cubic Interpolation. resolves #854

Cubic Interpolation is prone to overshooting during animations. This new feature allows for animations to be smooth but also doesn't overshoot the key frames.

The new function keeps the curve monotonic which means that the first derivative of the curve keeps the same sign, which cubic interpolation does not honor.

Here is the difference between Cubic and Monotonic Cubic for a curve going from 0 -> 90 -> 0 -> 0 -> 90.
image

screenrecording-2026-03-13_00-31-00.mp4

Summary by CodeRabbit

  • New Features
    • Added monotonic cubic interpolation (angle- and time-aware variants) for scalars, vectors, quaternions and Variant; new spherical quaternion monotonic interpolation; new animation interpolation modes and editor menu options; GLTF export maps them to cubic spline.
  • Documentation
    • API docs updated to include the new monotonic cubic interpolation methods.
  • Tests
    • Added unit tests covering monotonic cubic interpolation variants.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16eca558-c345-479c-811e-04ed2a176789

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds monotonic Hermite-based cubic interpolation across Math, vector/quaternion types, Variant bindings, Animation runtime and editor UI; introduces INTERPOLATION_CUBIC_MONOTONIC and INTERPOLATION_CUBIC_MONOTONIC_ANGLE, time- and angle-aware variants, docs, and tests.

Changes

Cohort / File(s) Summary
Core Math Functions
core/math/math_funcs.h
Added templated monotonic cubic interpolation utilities: monotonic_cubic_interpolate, monotonic_cubic_interpolate_in_time, and angle variants with monotonic tangent computation and Hermite basis.
Vector & Quaternion Types
core/math/vector2.h, core/math/vector3.h, core/math/vector4.h, core/math/vector4.cpp, core/math/quaternion.h, core/math/quaternion.cpp
Added per-type monotonic cubic interpolation methods and time-aware variants. Quaternion methods use Expmap/log-space per-component monotonic interpolation and blend results via slerp.
Variant Layer & Utilities
core/variant/variant_call.cpp, core/variant/variant_utility.h, core/variant/variant_utility.cpp
Registered built-in monotonic_cubic_interpolate / _in_time / angle variants and added VariantUtility wrappers exposing Math implementations to scripting.
Animation Runtime
scene/resources/animation.h, scene/resources/animation.cpp
Added INTERPOLATION_CUBIC_MONOTONIC and INTERPOLATION_CUBIC_MONOTONIC_ANGLE; implemented per-type _monotonic_cubic_interpolate_in_time overloads and Variant dispatch; integrated monotonic cases into _interpolate, optimization, and enum bindings.
Editor: Animation Track UI
editor/animation/animation_track_editor.h, editor/animation/animation_track_editor.cpp
Added menu constants and UI entries for Monotonic Cubic / Monotonic Cubic Angle; extended interpolation selection, insertion, and angle-detection branches to include monotonic variants.
GLTF Mapping
modules/gltf/structures/gltf_animation.cpp
Mapped INTERPOLATION_CUBIC_MONOTONIC and INTERPOLATION_CUBIC_MONOTONIC_ANGLE to GLTF cubic spline export (INTERP_CUBIC_SPLINE).
Documentation
doc/classes/@GlobalScope.xml, doc/classes/Vector2.xml, doc/classes/Vector3.xml, doc/classes/Vector4.xml, doc/classes/Quaternion.xml, doc/classes/Animation.xml
Added docs for new monotonic cubic interpolation functions (regular, angle, and in_time variants) and new Animation interpolation enum constants.
Tests
tests/core/math/test_math_funcs.h
Added templated tests covering monotonic_cubic_interpolate, angle variants, and in_time variants for float/double.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Editor (Track UI)
    participant Animation as Animation Runtime
    participant Type as Vector/Quaternion
    participant Math as Math::monotonic_cubic*
    participant Variant as Variant Bindings

    Editor->>Animation: select/insert key (MONOTONIC)
    Animation->>Type: _interpolate(dispatch INTERPOLATION_CUBIC_MONOTONIC)
    Type->>Math: call monotonic_cubic_interpolate(_in_time / _angle)
    Math-->>Type: per-component interpolated values
    Type-->>Animation: composed Vector/Quaternion result
    Animation-->>Variant: expose result to scripting / export
    Variant-->>Editor: script-accessible interpolation APIs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding monotonic cubic interpolation functionality to resolve the issue of overshoot in animation playback.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #854 by implementing monotonic cubic interpolation to prevent overshoot in animation playback. All coding requirements are met: monotonic interpolation functions added across Math namespace, Vector2/3/4, Quaternion, Animation system, and exposed to Variant/GDScript APIs.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing monotonic cubic interpolation. Documentation, editor UI updates for the new interpolation option, tests, and GLTF mapping are all necessary components supporting the core feature addition.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
editor/animation/animation_track_editor.cpp (1)

3092-3117: ⚠️ Potential issue | 🔴 Critical

Add icon handling for the new interpolation enums.

These modes are now selectable, but the draw path at Lines 2315-2321 and Line 2395 still uses a 5-entry interp_icon lookup. That no longer matches the interpolation enum, so monotonic tracks will show the wrong icon or read past the array during redraw.

Suggested fix
-				Ref<Texture2D> interp_icon[5] = {
-					get_editor_theme_icon(SNAME("InterpRaw")),
-					get_editor_theme_icon(SNAME("InterpLinear")),
-					get_editor_theme_icon(SNAME("InterpCubic")),
-					get_editor_theme_icon(SNAME("InterpLinearAngle")),
-					get_editor_theme_icon(SNAME("InterpCubicAngle")),
-				};
@@
-					Ref<Texture2D> icon = interp_icon[interp_mode];
+					Ref<Texture2D> icon;
+					switch (interp_mode) {
+						case Animation::INTERPOLATION_NEAREST:
+							icon = get_editor_theme_icon(SNAME("InterpRaw"));
+							break;
+						case Animation::INTERPOLATION_LINEAR:
+							icon = get_editor_theme_icon(SNAME("InterpLinear"));
+							break;
+						case Animation::INTERPOLATION_CUBIC:
+						case Animation::INTERPOLATION_CUBIC_MONOTONIC:
+							icon = get_editor_theme_icon(SNAME("InterpCubic"));
+							break;
+						case Animation::INTERPOLATION_LINEAR_ANGLE:
+							icon = get_editor_theme_icon(SNAME("InterpLinearAngle"));
+							break;
+						case Animation::INTERPOLATION_CUBIC_ANGLE:
+						case Animation::INTERPOLATION_CUBIC_MONOTONIC_ANGLE:
+							icon = get_editor_theme_icon(SNAME("InterpCubicAngle"));
+							break;
+						default:
+							icon = get_editor_theme_icon(SNAME("InterpRaw"));
+							break;
+					}

Also applies to: 3568-3571

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/animation/animation_track_editor.cpp` around lines 3092 - 3117, The
draw code still uses a 5-entry interp_icon lookup which no longer matches the
expanded interpolation enum (new monotonic entries), causing wrong icons/UB;
update the interp_icon array and any switch/lookup in the drawing code to
include icons for MENU_INTERPOLATION_CUBIC_MONOTONIC and
MENU_INTERPOLATION_CUBIC_MONOTONIC_ANGLE (and their angle/non-angle
counterparts), and adjust any index mapping logic so each interpolation enum
value maps to the correct icon (update references to interp_icon and the drawing
functions that read interpolation enum values). Ensure you add the corresponding
get_editor_theme_icon calls for the new monotonic variants and verify the lookup
size matches the enum range so no out-of-bounds access occurs.
scene/resources/animation.h (1)

62-70: ⚠️ Potential issue | 🔴 Critical

Do not renumber existing InterpolationType enum values.

Adding INTERPOLATION_CUBIC_MONOTONIC before existing entries changes legacy numeric IDs (notably INTERPOLATION_LINEAR_ANGLE and INTERPOLATION_CUBIC_ANGLE). That can break backward compatibility for serialized animation tracks and runtime interpolation selection.

🔧 Proposed fix
 	enum InterpolationType : uint8_t {
 		INTERPOLATION_NEAREST,
 		INTERPOLATION_LINEAR,
 		INTERPOLATION_CUBIC,
-		INTERPOLATION_CUBIC_MONOTONIC,
 		INTERPOLATION_LINEAR_ANGLE,
 		INTERPOLATION_CUBIC_ANGLE,
+		INTERPOLATION_CUBIC_MONOTONIC,
 		INTERPOLATION_CUBIC_MONOTONIC_ANGLE,
 	};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scene/resources/animation.h` around lines 62 - 70, The enum InterpolationType
was modified in a way that shifts numeric IDs (specifically
INTERPOLATION_LINEAR_ANGLE and INTERPOLATION_CUBIC_ANGLE); restore backward
compatibility by ensuring existing values retain their original numeric
IDs—either move INTERPOLATION_CUBIC_MONOTONIC to the end of the enum list or add
explicit integer assignments for each enum entry so INTERPOLATION_NEAREST,
INTERPOLATION_LINEAR, INTERPOLATION_CUBIC, INTERPOLATION_LINEAR_ANGLE, and
INTERPOLATION_CUBIC_ANGLE keep their previous numeric values; update the
InterpolationType definition accordingly to avoid changing legacy serialized
IDs.
🧹 Nitpick comments (2)
core/math/quaternion.cpp (1)

269-368: Extract the shared spherical-cubic core.

These two overloads duplicate the full phase-alignment and dual-expmap flow from spherical_cubic_interpolate{_in_time}. A small helper that takes the scalar interpolation op would keep future fixes to the quaternion math in one place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/math/quaternion.cpp` around lines 269 - 368, Both
spherical_monotonic_cubic_interpolate and
spherical_monotonic_cubic_interpolate_in_time duplicate the phase-alignment,
flip handling, dual ExpMap and slerp blending logic; extract that shared flow
into a single helper (e.g., spherical_monotonic_cubic_interpolate_core) that
accepts a scalar interpolation callable (std::function or template parameter)
and any optional time arguments, so the two public methods call the core with
either Math::monotonic_cubic_interpolate or
Math::monotonic_cubic_interpolate_in_time; update the core to perform Basis
conversions, flip adjustments (from_q, pre_q, to_q, post_q), compute ln vectors
in both from_q and to_q spaces, call the provided scalar interpolator for x/y/z,
build q1/q2 and return q1.slerp(q2, p_weight), keeping the public function names
spherical_monotonic_cubic_interpolate and
spherical_monotonic_cubic_interpolate_in_time as thin wrappers.
scene/resources/animation.cpp (1)

6566-6754: Extract the shared Variant cubic dispatcher before these two implementations drift.

Animation::monotonic_cubic_interpolate_in_time_variant is effectively a second copy of Animation::cubic_interpolate_in_time_variant. Any future type fix now has to be mirrored across another very large switch and array-walking block. I'd pull the shared traversal into a helper and inject the cubic vs monotonic primitives.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scene/resources/animation.cpp` around lines 6566 - 6754, The two large
switch+array traversal blocks in
Animation::monotonic_cubic_interpolate_in_time_variant and
Animation::cubic_interpolate_in_time_variant should be refactored into a single
helper (e.g., Animation::variant_cubic_dispatcher) that accepts a strategy flag
or function pointers for the primitive scalar/vector interpolation routines
(e.g., Math::cubic_interpolate_in_time vs
Math::monotonic_cubic_interpolate_in_time and the instance methods like
Vector2::monotonic_cubic_interpolate_in_time / cubic_interpolate_in_time,
Quaternion::spherical_monotonic_cubic_interpolate_in_time /
spherical_cubic_interpolate_in_time). Move the shared array handling and
type-switch logic into that helper and have the two public methods call it with
the appropriate primitive functions; ensure you preserve the special-casing
(numeric cast_to_blendwise/cast_from_blendwise, string fallback,
PACKED_BYTE_ARRAY skip, p_snap_array_element behavior) by passing any needed
flags through the helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/math/math_funcs.h`:
- Around line 947-960: The function monotonic_cubic_interpolate_angle_in_time
has the timestamp parameters in the wrong order (p_pre_t, p_to_t, p_post_t)
which mismatches the established _in_time API; change the signature to accept (T
p_to_t, T p_pre_t, T p_post_t) and update the call to
monotonic_cubic_interpolate_in_time(...) to pass the timestamps in the same
order expected by that function (p_weight, p_to_t, p_pre_t, p_post_t) so pre/to
timestamps are not swapped; ensure the parameter names in the function
declaration/definition and any callers use the corrected order to preserve API
consistency with cubic_interpolate_angle_in_time and VariantUtilityFunctions.

In `@core/variant/variant_utility.cpp`:
- Around line 548-550: The wrapper
VariantUtilityFunctions::monotonic_cubic_interpolate_angle_in_time forwards time
arguments to Math::monotonic_cubic_interpolate_angle_in_time in the wrong order,
swapping to_t and pre_t; fix it by calling
Math::monotonic_cubic_interpolate_angle_in_time(from, to, pre, post, weight,
pre_t, to_t, post_t) so that the forwarded times match Math's expected (p_pre_t,
p_to_t, p_post_t) parameter order.

In `@tests/core/math/test_math_funcs.h`:
- Around line 694-700: The test block is calling
Math::cubic_interpolate_angle_in_time instead of the intended
Math::monotonic_cubic_interpolate_angle_in_time; update all five CHECK lines to
call Math::monotonic_cubic_interpolate_angle_in_time (keeping the same argument
order) so the monotonic helper is exercised, then re-run tests and update the
expected doctest::Approx values to the correct baselines produced by the
monotonic implementation.

---

Outside diff comments:
In `@editor/animation/animation_track_editor.cpp`:
- Around line 3092-3117: The draw code still uses a 5-entry interp_icon lookup
which no longer matches the expanded interpolation enum (new monotonic entries),
causing wrong icons/UB; update the interp_icon array and any switch/lookup in
the drawing code to include icons for MENU_INTERPOLATION_CUBIC_MONOTONIC and
MENU_INTERPOLATION_CUBIC_MONOTONIC_ANGLE (and their angle/non-angle
counterparts), and adjust any index mapping logic so each interpolation enum
value maps to the correct icon (update references to interp_icon and the drawing
functions that read interpolation enum values). Ensure you add the corresponding
get_editor_theme_icon calls for the new monotonic variants and verify the lookup
size matches the enum range so no out-of-bounds access occurs.

In `@scene/resources/animation.h`:
- Around line 62-70: The enum InterpolationType was modified in a way that
shifts numeric IDs (specifically INTERPOLATION_LINEAR_ANGLE and
INTERPOLATION_CUBIC_ANGLE); restore backward compatibility by ensuring existing
values retain their original numeric IDs—either move
INTERPOLATION_CUBIC_MONOTONIC to the end of the enum list or add explicit
integer assignments for each enum entry so INTERPOLATION_NEAREST,
INTERPOLATION_LINEAR, INTERPOLATION_CUBIC, INTERPOLATION_LINEAR_ANGLE, and
INTERPOLATION_CUBIC_ANGLE keep their previous numeric values; update the
InterpolationType definition accordingly to avoid changing legacy serialized
IDs.

---

Nitpick comments:
In `@core/math/quaternion.cpp`:
- Around line 269-368: Both spherical_monotonic_cubic_interpolate and
spherical_monotonic_cubic_interpolate_in_time duplicate the phase-alignment,
flip handling, dual ExpMap and slerp blending logic; extract that shared flow
into a single helper (e.g., spherical_monotonic_cubic_interpolate_core) that
accepts a scalar interpolation callable (std::function or template parameter)
and any optional time arguments, so the two public methods call the core with
either Math::monotonic_cubic_interpolate or
Math::monotonic_cubic_interpolate_in_time; update the core to perform Basis
conversions, flip adjustments (from_q, pre_q, to_q, post_q), compute ln vectors
in both from_q and to_q spaces, call the provided scalar interpolator for x/y/z,
build q1/q2 and return q1.slerp(q2, p_weight), keeping the public function names
spherical_monotonic_cubic_interpolate and
spherical_monotonic_cubic_interpolate_in_time as thin wrappers.

In `@scene/resources/animation.cpp`:
- Around line 6566-6754: The two large switch+array traversal blocks in
Animation::monotonic_cubic_interpolate_in_time_variant and
Animation::cubic_interpolate_in_time_variant should be refactored into a single
helper (e.g., Animation::variant_cubic_dispatcher) that accepts a strategy flag
or function pointers for the primitive scalar/vector interpolation routines
(e.g., Math::cubic_interpolate_in_time vs
Math::monotonic_cubic_interpolate_in_time and the instance methods like
Vector2::monotonic_cubic_interpolate_in_time / cubic_interpolate_in_time,
Quaternion::spherical_monotonic_cubic_interpolate_in_time /
spherical_cubic_interpolate_in_time). Move the shared array handling and
type-switch logic into that helper and have the two public methods call it with
the appropriate primitive functions; ensure you preserve the special-casing
(numeric cast_to_blendwise/cast_from_blendwise, string fallback,
PACKED_BYTE_ARRAY skip, p_snap_array_element behavior) by passing any needed
flags through the helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a60ef8ef-5981-48e7-9a61-c26f77ab2797

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff8093 and 3e8190e.

📒 Files selected for processing (15)
  • core/math/math_funcs.h
  • core/math/quaternion.cpp
  • core/math/quaternion.h
  • core/math/vector2.h
  • core/math/vector3.h
  • core/math/vector4.cpp
  • core/math/vector4.h
  • core/variant/variant_call.cpp
  • core/variant/variant_utility.cpp
  • core/variant/variant_utility.h
  • editor/animation/animation_track_editor.cpp
  • editor/animation/animation_track_editor.h
  • scene/resources/animation.cpp
  • scene/resources/animation.h
  • tests/core/math/test_math_funcs.h

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
doc/classes/Vector4.xml (1)

99-122: Optional: clarify monotonicity is per-component.

This avoids readers assuming path-level monotonicity for the whole vector.

Wording tweak
- Performs a monotonic cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight].
+ Performs a per-component monotonic cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight].
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/classes/Vector4.xml` around lines 99 - 122, Update the descriptions for
the methods monotonic_cubic_interpolate and monotonic_cubic_interpolate_in_time
in Vector4 documentation to explicitly state that "monotonic" is applied
per-component (each vector component is interpolated independently), so readers
don't assume the interpolation guarantees monotonicity along the whole vector
path; mention that the monotonicity guarantee applies component-wise rather than
across the combined vector trajectory.
tests/core/math/test_math_funcs.h (1)

672-700: Add an invariant-style regression for “no overshoot”.

The current assertions are mostly snapshot values. Adding a boundedness check against keyframe endpoints would better lock in the issue #854 guarantee.

Proposed test addition
+TEST_CASE_TEMPLATE("[Math] monotonic_cubic_interpolate no_overshoot_regression", T, float, double) {
+	const T v1 = Math::monotonic_cubic_interpolate((T)90.0, (T)0.0, (T)0.0, (T)0.0, (T)0.25);
+	const T v2 = Math::monotonic_cubic_interpolate((T)90.0, (T)0.0, (T)0.0, (T)0.0, (T)0.5);
+	const T v3 = Math::monotonic_cubic_interpolate((T)90.0, (T)0.0, (T)0.0, (T)0.0, (T)0.75);
+
+	CHECK(v1 <= (T)90.0);
+	CHECK(v1 >= (T)0.0);
+	CHECK(v2 <= (T)90.0);
+	CHECK(v2 >= (T)0.0);
+	CHECK(v3 <= (T)90.0);
+	CHECK(v3 >= (T)0.0);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/math/test_math_funcs.h` around lines 672 - 700, Add invariant "no
overshoot" checks to the existing tests for monotonic_cubic_interpolate_angle,
monotonic_cubic_interpolate_in_time, and
monotonic_cubic_interpolate_angle_in_time: for each call already made in those
TEST_CASE_TEMPLATE blocks, add an assertion that the returned value is bounded
between the start and end keyframe values (use min(start,end) and
max(start,end)); for the angle variants compute the shortest-interval normalized
endpoints (or unwrap so start/end define the intended interval) and assert the
interpolated angle lies within that unwrapped interval; use the existing doctest
CHECK macros (e.g. CHECK(result >= min && result <= max)) so the tests enforce
no-overshoot invariants in addition to the snapshot assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@doc/classes/Quaternion.xml`:
- Around line 219-233: Update the method descriptions to use quaternion
terminology: in the <method name="spherical_monotonic_cubic_interpolate"> and
<method name="spherical_monotonic_cubic_interpolate_in_time"> descriptions,
replace the phrase “this vector” with “this quaternion” so the docs consistently
refer to quaternions (e.g., change both occurrences inside those <description>
tags).

---

Nitpick comments:
In `@doc/classes/Vector4.xml`:
- Around line 99-122: Update the descriptions for the methods
monotonic_cubic_interpolate and monotonic_cubic_interpolate_in_time in Vector4
documentation to explicitly state that "monotonic" is applied per-component
(each vector component is interpolated independently), so readers don't assume
the interpolation guarantees monotonicity along the whole vector path; mention
that the monotonicity guarantee applies component-wise rather than across the
combined vector trajectory.

In `@tests/core/math/test_math_funcs.h`:
- Around line 672-700: Add invariant "no overshoot" checks to the existing tests
for monotonic_cubic_interpolate_angle, monotonic_cubic_interpolate_in_time, and
monotonic_cubic_interpolate_angle_in_time: for each call already made in those
TEST_CASE_TEMPLATE blocks, add an assertion that the returned value is bounded
between the start and end keyframe values (use min(start,end) and
max(start,end)); for the angle variants compute the shortest-interval normalized
endpoints (or unwrap so start/end define the intended interval) and assert the
interpolated angle lies within that unwrapped interval; use the existing doctest
CHECK macros (e.g. CHECK(result >= min && result <= max)) so the tests enforce
no-overshoot invariants in addition to the snapshot assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2f79e7c0-d82b-4f2c-b2ff-3c76cf1f03ee

📥 Commits

Reviewing files that changed from the base of the PR and between c6c7be0 and 40de93f.

📒 Files selected for processing (7)
  • core/math/math_funcs.h
  • doc/classes/@GlobalScope.xml
  • doc/classes/Quaternion.xml
  • doc/classes/Vector2.xml
  • doc/classes/Vector3.xml
  • doc/classes/Vector4.xml
  • tests/core/math/test_math_funcs.h

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/core/math/test_math_funcs.h (2)

658-670: Add a held-segment regression to the scalar helper.

The monotonic logic in core/math/math_funcs.h:832-878 matters most when a flat p_from == p_to segment would otherwise inherit slope from its neighbors. This block still only covers a rising segment plus extrapolation, so it doesn't lock the no-overshoot case behind GH-854.

Example regression to add
+ CHECK(Math::monotonic_cubic_interpolate((T)0.0, (T)0.0, (T)1.0, (T)1.0, (T)0.25) == doctest::Approx((T)0.0));
+ CHECK(Math::monotonic_cubic_interpolate((T)0.0, (T)0.0, (T)1.0, (T)1.0, (T)0.5) == doctest::Approx((T)0.0));
+ CHECK(Math::monotonic_cubic_interpolate((T)0.0, (T)0.0, (T)1.0, (T)1.0, (T)0.75) == doctest::Approx((T)0.0));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/math/test_math_funcs.h` around lines 658 - 670, The test suite
needs a held-segment regression: add cases to the TEST_CASE_TEMPLATE for
Math::monotonic_cubic_interpolate that exercise a flat segment (p_from == p_to)
so the scalar helper locks a zero slope instead of inheriting neighbor slopes;
then update the monotonic cubic logic in core/math/math_funcs.h (function
Math::monotonic_cubic_interpolate / the scalar helper in that file) to detect
p_from == p_to and set the segment derivatives to zero (and handle extrapolation
correctly) so flat segments do not acquire nonzero slopes from neighbors or
produce overshoot.

694-700: Mirror the held-angle case in _angle_in_time.

The plain _angle block above already protects a flat 0 -> 0 segment with non-zero neighbors, but this time-aware variant only checks a smooth increasing arc. Since the animation regression is about playback on flat segments, adding the same plateau case here would make this the real regression guard.

Example regression to add
+ CHECK(Math::monotonic_cubic_interpolate_angle_in_time((T)0.0, (T)0.0, (T)(Math::PI / 2.0), (T)(Math::PI / 2.0), (T)0.25, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.0));
+ CHECK(Math::monotonic_cubic_interpolate_angle_in_time((T)0.0, (T)0.0, (T)(Math::PI / 2.0), (T)(Math::PI / 2.0), (T)0.5, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.0));
+ CHECK(Math::monotonic_cubic_interpolate_angle_in_time((T)0.0, (T)0.0, (T)(Math::PI / 2.0), (T)(Math::PI / 2.0), (T)0.75, (T)0.5, (T)0.0, (T)1.0) == doctest::Approx((T)0.0));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/math/test_math_funcs.h` around lines 694 - 700,
monotonic_cubic_interpolate_angle_in_time is missing the plateau/held-angle
guard that _angle_in_time has; add the same early-return case to detect a flat
segment (e.g., when start and end angles produce a zero delta across the
interval while neighbors are non-zero) and return the held angle so playback on
flat segments remains stable; update the logic in
monotonic_cubic_interpolate_angle_in_time (the time-aware interpolation routine)
to mirror the held-angle protection present in _angle_in_time so 0->0 segments
with non-zero neighbors are preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/core/math/test_math_funcs.h`:
- Around line 658-670: The test suite needs a held-segment regression: add cases
to the TEST_CASE_TEMPLATE for Math::monotonic_cubic_interpolate that exercise a
flat segment (p_from == p_to) so the scalar helper locks a zero slope instead of
inheriting neighbor slopes; then update the monotonic cubic logic in
core/math/math_funcs.h (function Math::monotonic_cubic_interpolate / the scalar
helper in that file) to detect p_from == p_to and set the segment derivatives to
zero (and handle extrapolation correctly) so flat segments do not acquire
nonzero slopes from neighbors or produce overshoot.
- Around line 694-700: monotonic_cubic_interpolate_angle_in_time is missing the
plateau/held-angle guard that _angle_in_time has; add the same early-return case
to detect a flat segment (e.g., when start and end angles produce a zero delta
across the interval while neighbors are non-zero) and return the held angle so
playback on flat segments remains stable; update the logic in
monotonic_cubic_interpolate_angle_in_time (the time-aware interpolation routine)
to mirror the held-angle protection present in _angle_in_time so 0->0 segments
with non-zero neighbors are preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85dee939-3bf7-45ee-8c46-9a8ad30cfec3

📥 Commits

Reviewing files that changed from the base of the PR and between 40de93f and 2616487.

⛔ Files ignored due to path filters (1)
  • editor/icons/InterpMonotonic.svg is excluded by !**/*.svg
📒 Files selected for processing (2)
  • editor/animation/animation_track_editor.cpp
  • tests/core/math/test_math_funcs.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • editor/animation/animation_track_editor.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
doc/classes/Animation.xml (1)

744-746: Add the angle-mode normalization note for consistency.

Line 744 introduces another *_ANGLE interpolation type, but unlike Line 733-740 it doesn’t mention normalization behavior. If behavior matches other angle interpolations, documenting that caveat here avoids confusion.

📘 Suggested doc tweak
 		<constant name="INTERPOLATION_CUBIC_MONOTONIC_ANGLE" value="6" enum="InterpolationType">
 			Monotonic Cubic interpolation with shortest path rotation.
+			[b]Note:[/b] The result value is always normalized and may not match the key value.
 		</constant>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/classes/Animation.xml` around lines 744 - 746, Add the same
angle-normalization note to the INTERPOLATION_CUBIC_MONOTONIC_ANGLE constant
documentation: state that this interpolation uses shortest-path rotation and
input/output angles are normalized (e.g., wrapped to -180..180 or consistent
internal angle range) prior to interpolation, mirroring the wording used for the
other *_ANGLE entries; update the <constant
name="INTERPOLATION_CUBIC_MONOTONIC_ANGLE"> description to include that caveat
so behavior is consistent with the other angle interpolation docs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@doc/classes/`@GlobalScope.xml:
- Around line 792-829: The descriptions for
monotonic_cubic_interpolate_angle_in_time and
monotonic_cubic_interpolate_in_time incorrectly imply the interpolation
factor/parameter is derived from the time arguments; update both method
descriptions to state that the interpolation factor ("weight") is provided by
the caller and that the time parameters (to_t, pre_t, post_t) are used only to
normalize/shape the interpolation and compute time-aware tangents to preserve
monotonicity and prevent overshoot when keyframes are unevenly spaced.

---

Nitpick comments:
In `@doc/classes/Animation.xml`:
- Around line 744-746: Add the same angle-normalization note to the
INTERPOLATION_CUBIC_MONOTONIC_ANGLE constant documentation: state that this
interpolation uses shortest-path rotation and input/output angles are normalized
(e.g., wrapped to -180..180 or consistent internal angle range) prior to
interpolation, mirroring the wording used for the other *_ANGLE entries; update
the <constant name="INTERPOLATION_CUBIC_MONOTONIC_ANGLE"> description to include
that caveat so behavior is consistent with the other angle interpolation docs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13f324dc-6a25-4f72-8c00-274cb643f332

📥 Commits

Reviewing files that changed from the base of the PR and between 2616487 and 9c05eda.

⛔ Files ignored due to path filters (1)
  • editor/icons/InterpMonotonic.svg is excluded by !**/*.svg
📒 Files selected for processing (6)
  • doc/classes/@GlobalScope.xml
  • doc/classes/Animation.xml
  • doc/classes/Vector2.xml
  • doc/classes/Vector3.xml
  • doc/classes/Vector4.xml
  • scene/resources/animation.h

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3D Animation play back is wrong when interpolation is cubic

2 participants